#ifndef SDK_CAMERA_MAN
#define SDK_CAMERA_MAN
#include "ModelConfig.h"
#include <daisy\IInputManager.h>
using namespace Ogre;
class ExtendedCamera {
	// Attributes ------------------------------------------------------------------------------ 
protected: 
	SceneNode *mTargetNode; 
	// The camera target
	SceneNode *mCameraNode;
	// The camera itself 
	Camera *mCamera;
	// Ogre camera 
	SceneManager *mSceneMgr; 
		   
	String mName;  
		  
	bool mOwnCamera; 
	// To know if the ogre camera binded has been created outside or inside of this class  
	Real mTightness; 
	// Determines the movement of the camera - 1 means tight movement, while 0 means no movement 
public: 
	ExtendedCamera (String name, SceneManager *sceneMgr, Camera *camera = 0) 
	{
		// Basic member references setup 
		mName = name; 
		mSceneMgr = sceneMgr; 
		// Create the camera's node structure 
		mCameraNode = mSceneMgr->getRootSceneNode ()->createChildSceneNode (mName); 
		mTargetNode = mSceneMgr->getRootSceneNode ()->createChildSceneNode (mName + "_target");
		mCameraNode->setAutoTracking (true, mTargetNode);
		// The camera will always look at the camera target 
		mCameraNode->setFixedYawAxis (true); 
		// Needed because of auto tracking  
		// Create our camera if it wasn't passed as a parameter
		if (camera == 0) 
		{
			mCamera = mSceneMgr->createCamera (mName); 
			mOwnCamera = true;
		} else 
		{ 
			mCamera = camera;
		// just to make sure that mCamera is set to 'origin' (same position as the mCameraNode) 
		
			//mCamera->setPosition(0.0,0.0,0.0); 
		
			mOwnCamera = false; 
		}
		// ... and attach the Ogre camera to the camera node
		mCameraNode->attachObject (mCamera);  
		// Default tightness 
		mTightness = 0.01f; 
	} 
	~ExtendedCamera () 
	{
		mCameraNode->detachAllObjects ();
		if (mOwnCamera) 
			delete mCamera;
		mSceneMgr->destroySceneNode (mName);
		mSceneMgr->destroySceneNode (mName + "_target");
	}  
	void setTightness (Real tightness)
	{ 
		mTightness = tightness;
	} 
	Real getTightness () 
	{ 
		return mTightness;
	}  
	Vector3 getCameraPosition () 
	{ 
		return mCameraNode->getPosition ();
	}  
	void instantUpdate (Vector3 cameraPosition, Vector3 targetPosition) 
	{ 
		mCameraNode->setPosition (cameraPosition); 
		mTargetNode->setPosition (targetPosition);
	}  
	void update (Real elapsedTime, Vector3 cameraPosition, Vector3 targetPosition)
	{ 
		// Handle movement 
		Vector3 displacement;  
		displacement = (cameraPosition - mCameraNode->getPosition ()) * mTightness;
		mCameraNode->translate (displacement); 
		displacement = (targetPosition - mTargetNode->getPosition ()) * mTightness;
		mTargetNode->translate (displacement);
	} 
};


class SdkCameraMan
    {
		
    public:
		enum CameraStyle   // enumerator values for different styles of camera movement
	{
		CS_FREELOOK,
		CS_ORBIT,
		CS_MANUAL,
		CS_3RDFIX,
		CS_3RDCHASE,
		CS_FIRST
	};
		SdkCameraMan(SceneNode* mainNode = 0,SceneNode* sightNode = 0,SceneNode* tarCameraNode = 0,Ogre::Camera* cam = 0)
		: mCamera(0)
		, mTarget(0)
		, mOrbiting(false)
		, mZooming(false)
		, mTopSpeed(150)
		, mVelocity(Ogre::Vector3::ZERO)
		, mGoingForward(false)
		, mGoingBack(false)
		, mGoingLeft(false)
		, mGoingRight(false)
		, mGoingUp(false)
		, mGoingDown(false)
		, mFastMove(false)
		, _mainNode(mainNode)
		, _sightNode(sightNode)
		, _tarCameraNode(tarCameraNode)
		{

			setCamera(cam);
			//setStyle(CS_FREELOOK);
		}

		virtual ~SdkCameraMan() {}

		/*-----------------------------------------------------------------------------
		| Swaps the camera on our camera man for another camera.
		-----------------------------------------------------------------------------*/
		virtual void setCamera(Ogre::Camera* cam)
		{
			mCamera = cam;
		}

		virtual Ogre::Camera* getCamera()
		{
			return mCamera;
		}

		/*-----------------------------------------------------------------------------
		| Sets the target we will revolve around. Only applies for orbit style.
		-----------------------------------------------------------------------------*/
		virtual void setTarget(Ogre::SceneNode* target)
		{
			if (target != mTarget)
			{
				mTarget = target;
				if(target)
				{
					setYawPitchDist(Ogre::Degree(0), Ogre::Degree(15), 150);
					mCamera->setAutoTracking(true, mTarget);
				}
				else
				{
					mCamera->setAutoTracking(false);
				}

			}


		}

		virtual Ogre::SceneNode* getTarget()
		{
			return mTarget;
		}

		/*-----------------------------------------------------------------------------
		| Sets the spatial offset from the target. Only applies for orbit style.
		-----------------------------------------------------------------------------*/
		virtual void setYawPitchDist(Ogre::Radian yaw, Ogre::Radian pitch, Ogre::Real dist)
		{
			mCamera->setPosition(mTarget->_getDerivedPosition());
			mCamera->setOrientation(mTarget->_getDerivedOrientation());
			mCamera->yaw(yaw);
			mCamera->pitch(-pitch);
			mCamera->moveRelative(Ogre::Vector3(0, 0, dist));
		}

		/*-----------------------------------------------------------------------------
		| Sets the camera's top speed. Only applies for free-look style.
		-----------------------------------------------------------------------------*/
		virtual void setTopSpeed(Ogre::Real topSpeed)
		{
			mTopSpeed = topSpeed;
		}

		virtual Ogre::Real getTopSpeed()
		{
			return mTopSpeed;
		}

		/*-----------------------------------------------------------------------------
		| Sets the movement style of our camera man.
		-----------------------------------------------------------------------------*/
		virtual void setStyle(CameraStyle style)
		{
			if (mStyle != CS_ORBIT && style == CS_ORBIT)
			{
				setTarget(mTarget ? mTarget : mCamera->getSceneManager()->getRootSceneNode());
				mCamera->setFixedYawAxis(true);
				manualStop();
				setYawPitchDist(Ogre::Degree(0), Ogre::Degree(15), 150);

			}
			else if (mStyle != CS_FREELOOK && style == CS_FREELOOK)
			{
				mCamera->setAutoTracking(false);
				mCamera->setFixedYawAxis(true);
			}
			else if (mStyle != CS_MANUAL && style == CS_MANUAL)
			{
				mCamera->setAutoTracking(false);
				manualStop();
			}
			mStyle = style;

		}

		virtual CameraStyle getStyle()
		{
			return mStyle;
		}

		/*-----------------------------------------------------------------------------
		| Manually stops the camera when in free-look mode.
		-----------------------------------------------------------------------------*/
		virtual void manualStop()
		{
			if (mStyle == CS_FREELOOK)
			{
				mGoingForward = false;
				mGoingBack = false;
				mGoingLeft = false;
				mGoingRight = false;
				mGoingUp = false;
				mGoingDown = false;
				mVelocity = Ogre::Vector3::ZERO;
			}
		}

		virtual bool frameRenderingQueued(const DAISY::TimeType interval)
		{
			Ogre::Real _interval = interval;
			if(interval <0.015)
				_interval = 0.015;
			if (mStyle == CS_FREELOOK)
			{
				// build our acceleration vector based on keyboard input composite
				Ogre::Vector3 accel = Ogre::Vector3::ZERO;
				if (mGoingForward) accel += mCamera->getDirection();
				if (mGoingBack) accel -= mCamera->getDirection();
				if (mGoingRight) accel += mCamera->getRight();
				if (mGoingLeft) accel -= mCamera->getRight();
				if (mGoingUp) accel += mCamera->getUp();
				if (mGoingDown) accel -= mCamera->getUp();

				// if accelerating, try to reach top speed in a certain time
				Ogre::Real topSpeed = mFastMove ? mTopSpeed * 20 : mTopSpeed;
				if (accel.squaredLength() != 0)
				{
					accel.normalise();
					mVelocity += accel * topSpeed * _interval * 10;
				}
				// if not accelerating, try to stop in a certain time
				else mVelocity -= mVelocity * _interval * 10;

				Ogre::Real tooSmall = std::numeric_limits<Ogre::Real>::epsilon();

				// keep camera velocity below top speed and above epsilon
				if (mVelocity.squaredLength() > topSpeed * topSpeed)
				{
					mVelocity.normalise();
					mVelocity *= topSpeed;
				}
				else if (mVelocity.squaredLength() < tooSmall * tooSmall)
					mVelocity = Ogre::Vector3::ZERO;

				if (mVelocity != Ogre::Vector3::ZERO) mCamera->move(mVelocity * _interval);
			}

		if (_mainNode) 
		{
			if (_exCamera) 
			{ 
				switch (mStyle)
				{ 
				case CS_3RDCHASE: 
					// 3rd person chase 
					_exCamera->update (interval, _tarCameraNode->_getDerivedPosition(), _sightNode->_getDerivedPosition());
					break; 
				case CS_3RDFIX: 
					// 3rd person fixed 
					_exCamera->update (interval,  Vector3 (0, 200, 0),  _sightNode->_getDerivedPosition()); 
					break; 
				case CS_FIRST: 
					// 1st person 
					_exCamera->update (interval,  _tarCameraNode->_getDerivedPosition(),  _sightNode->_getDerivedPosition()); 
					break; 
				} 
			} 
		} 
		// 3rd Person - Chase Camera 
		
		





			return true;
		}

		/*-----------------------------------------------------------------------------
		| Processes key presses for free-look style movement.
		-----------------------------------------------------------------------------*/
		virtual void injectKeyDown(const DAISY::KeyEvent& evt)
		{
			if (mStyle == CS_FREELOOK)
			{
				if (evt.getKey() == DAISY::KC_W || evt.getKey() == DAISY::KC_UP) mGoingForward = true;
				else if (evt.getKey() == DAISY::KC_S || evt.getKey() == DAISY::KC_DOWN) mGoingBack = true;
				else if (evt.getKey() == DAISY::KC_A || evt.getKey() == DAISY::KC_LEFT) mGoingLeft = true;
				else if (evt.getKey() == DAISY::KC_D || evt.getKey() == DAISY::KC_RIGHT) mGoingRight = true;
				else if (evt.getKey() == DAISY::KC_PGUP) mGoingUp = true;
				else if (evt.getKey() == DAISY::KC_PGDOWN) mGoingDown = true;
				else if (evt.getKey() == DAISY::KC_LSHIFT) mFastMove = true;
			}
			if (evt.getKey() == DAISY::KC_F1) 
		{
			mStyle = CS_3RDCHASE;
			if (_mainNode) 
				_mainNode->setVisible (true);
			if (_exCamera) 
			{
				if (_mainNode) 
					_exCamera->instantUpdate (_tarCameraNode->_getDerivedPosition(), _sightNode->_getDerivedPosition()); 
				_exCamera->setTightness (0.1f); 
			} 
		} 
		// 3rd Person - Fixed Camera
		if (evt.getKey() == DAISY::KC_F2) 
		{
			mStyle = CS_3RDFIX; 
			if (_mainNode)
				_mainNode->setVisible (true);
			if (_exCamera) 
			{ 
				if (_mainNode) 
					_exCamera->instantUpdate (Vector3 (0, 200, 0), _sightNode->_getDerivedPosition()); 
				_exCamera->setTightness (0.1f); 
			}
		}
		// 1st Person
		if (evt.getKey() == DAISY::KC_F3)
		{ 
			mStyle = CS_FIRST;
			if (_mainNode) 
				_mainNode->setVisible (false);
			if (_exCamera) 
			{ 
				if (_mainNode)
					_exCamera->instantUpdate (_tarCameraNode->_getDerivedPosition(), _sightNode->_getDerivedPosition()); 
				_exCamera->setTightness (1.0f); 
			} 
		} 

		}

		/*-----------------------------------------------------------------------------
		| Processes key releases for free-look style movement.
		-----------------------------------------------------------------------------*/
		virtual void injectKeyUp(const DAISY::KeyEvent& evt)
		{
			if (mStyle == CS_FREELOOK)
			{
				if (evt.getKey() == DAISY::KC_W || evt.getKey() == DAISY::KC_UP) mGoingForward = false;
				else if (evt.getKey() == DAISY::KC_S || evt.getKey() == DAISY::KC_DOWN) mGoingBack = false;
				else if (evt.getKey() == DAISY::KC_A || evt.getKey() == DAISY::KC_LEFT) mGoingLeft = false;
				else if (evt.getKey() == DAISY::KC_D || evt.getKey() == DAISY::KC_RIGHT) mGoingRight = false;
				else if (evt.getKey() == DAISY::KC_PGUP) mGoingUp = false;
				else if (evt.getKey() == DAISY::KC_PGDOWN) mGoingDown = false;
				else if (evt.getKey() == DAISY::KC_LSHIFT) mFastMove = false;
			}
		}

		/*-----------------------------------------------------------------------------
		| Processes mouse movement differently for each style.
		-----------------------------------------------------------------------------*/
#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE
		virtual void injectMouseMove(const DAISY::MultiTouchEvent& evt)
#else
		virtual void injectMouseMove(const DAISY::MouseEvent& evt)
#endif
		{
			if (mStyle == CS_ORBIT)
			{
				Ogre::Real dist = (mCamera->getPosition() - mTarget->_getDerivedPosition()).length();

				if (mOrbiting)   // yaw around the target, and pitch locally
				{
					mCamera->setPosition(mTarget->_getDerivedPosition());

					mCamera->yaw(Ogre::Degree(-evt.getX() * 0.25f));
					mCamera->pitch(Ogre::Degree(-evt.getY() * 0.25f));

					mCamera->moveRelative(Ogre::Vector3(0, 0, dist));

					// don't let the camera go over the top or around the bottom of the target
				}
				else if (mZooming)  // move the camera toward or away from the target
				{
					// the further the camera is, the faster it moves
					mCamera->moveRelative(Ogre::Vector3(0, 0, evt.getY() * 0.004f * dist));
				}
				else if (evt.getZ() != 0)  // move the camera toward or away from the target
				{
					// the further the camera is, the faster it moves
					mCamera->moveRelative(Ogre::Vector3(0, 0, -evt.getZ() * 0.0008f * dist));
				}
			}
			else if (mStyle == CS_FREELOOK)
			{
				mCamera->yaw(Ogre::Degree(-evt.getX() * 0.15f));
				mCamera->pitch(Ogre::Degree(-evt.getY() * 0.15f));
			}
		}

		/*-----------------------------------------------------------------------------
		| Processes mouse presses. Only applies for orbit style.
		| Left button is for orbiting, and right button is for zooming.
		-----------------------------------------------------------------------------*/
#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE
		virtual void injectMouseDown(const DAISY::MultiTouchEvent& evt)
		{
			if (mStyle == CS_ORBIT)
			{
                mOrbiting = true;
			}
		}
#else
		virtual void injectMouseDown(const DAISY::MouseEvent& evt)
		{
			if (mStyle == CS_ORBIT)
			{
				if (evt.getButton() == DAISY::MB_Left) mOrbiting = true;
				else if (evt.getButton() == DAISY::MB_Right) mZooming = true;
			}
		}
#endif

		/*-----------------------------------------------------------------------------
		| Processes mouse releases. Only applies for orbit style.
		| Left button is for orbiting, and right button is for zooming.
		-----------------------------------------------------------------------------*/
#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE
		virtual void injectMouseUp(const DAISY::MultiTouchEvent& evt)
		{
			if (mStyle == CS_ORBIT)
			{
                mOrbiting = false;
			}
		}
#else
		virtual void injectMouseUp(const DAISY::MouseEvent& evt)
		{
			if (mStyle == CS_ORBIT)
			{
				if (evt.getButton() == DAISY::MB_Left) mOrbiting = false;
				else if (evt.getButton() == DAISY::MB_Right) mZooming = false;
			}
		}
#endif
		void setTithness(Real value)
		{
			if(_exCamera)
			{
				_exCamera->setTightness(value);
			}
		}
		Real getTightness()
		{
			if(_exCamera)
				return _exCamera->getTightness();
			else
				return -1;
		}
		void setMainNode(SceneNode* node)
		{
			_mainNode = node;
		}
		SceneNode* getMainNode()
		{
			return _mainNode;
		}
		void setExCamera(ExtendedCamera* cam)
		{
			_exCamera = cam;
		}
		ExtendedCamera* getExCamera()
		{
			return _exCamera;
		}
		void setTarCameraNode(SceneNode* tarCam)
		{
			_tarCameraNode = tarCam;
		}
		SceneNode* getTarCamera()
		{
			return _tarCameraNode;
		}
		void setSightNode(SceneNode* sightNode)
		{
			_sightNode =sightNode;
		}
		SceneNode* getSightNode()
		{
			return _sightNode;
		}
    protected:

		Ogre::Camera* mCamera;
		CameraStyle mStyle;
		Ogre::SceneNode* mTarget;
		bool mOrbiting;
		bool mZooming;
		Ogre::Real mTopSpeed;
		Ogre::Vector3 mVelocity;
		bool mGoingForward;
		bool mGoingBack;
		bool mGoingLeft;
		bool mGoingRight;
		bool mGoingUp;
		bool mGoingDown;
		bool mFastMove;

		SceneNode* _mainNode;
		SceneNode* _tarCameraNode;
		SceneNode* _sightNode;
		ExtendedCamera* _exCamera;
    };

#endif